package com.sikulifang.bank.simulation.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Entity;
import cn.hutool.db.sql.SqlBuilder;
import com.sikulifang.bank.simulation.enums.BankEnum;
import com.sikulifang.bank.simulation.persistence.Bank;
import com.sikulifang.bank.simulation.server.service.IServerHandler;
import com.sikulifang.bank.simulation.server.service.impl.ServerHandlerImpl;
import com.sikulifang.bank.simulation.service.IBankService;
import com.sikulifang.bank.simulation.util.R;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

import static com.sikulifang.bank.simulation.util.IServerHandlerUtils.EXECUTOR_SERVICE;
import static com.sikulifang.bank.simulation.util.IServerHandlerUtils.SERVER_MAP;

/**
 * ClassName BankServiceImpl
 * Description BankServiceImpl
 *
 * @author Yat
 * Date 2023年6月9日, 0009 16:58
 * version 1.0
 */
@Slf4j
@Service
public class BankServiceImpl extends ServiceImpl implements IBankService {

    private ApplicationContext applicationContext;

    @Override
    public List<String> getQueryCriteria() {
        return ListUtil.toList("code", "port", "name");
    }

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public List<String> getBankCode() {
        return Arrays.stream(BankEnum.values())
                .map(value -> value.name() + StrPool.DASHED + value.getName())
                .sorted(Comparator.comparing(value -> value))
                .collect(Collectors.toList());
    }

    @Override
    @SneakyThrows
    public R<String> saveUpdate(Entity entity) {

        String name = entity.get("name", "");
        if (StrUtil.isNotBlank(name)) {
            List<String> split = StrUtil.split(name, StrPool.DASHED);
            entity.set("code", split.get(0));
            entity.set("name", split.get(1));
        }

        return super.saveUpdate(entity);
    }

    @SneakyThrows
    @Override
    public void start(Integer id) {

        Entity where = Entity.create(getTableName());
        where.set("id", id);
        Entity entity = db.get(where);
        Bank bank = entity.toBean(Bank.class);
        String key = bank.getCode() + bank.getPort();
        if (SERVER_MAP.containsKey(key)) {
            return;
        }
        IServerHandler handler = new ServerHandlerImpl(bank);
        handler.setApplicationContext(applicationContext);

        Future<?> future = EXECUTOR_SERVICE.submit(handler::start);
        try {
            future.get();
        } catch (Exception e) {
            throw new RuntimeException("启动服务失败");
        }
        SERVER_MAP.put(key, handler);
        db.update(Entity.create(getTableName()).set("status", Boolean.TRUE), where);
    }

    @Override
    @SneakyThrows
    public void stop(Integer id) {
        Entity where = Entity.create(getTableName());
        where.set("id", id);
        Entity entity = db.get(where);
        Bank bank = entity.toBean(Bank.class);
        String key = bank.getCode() + bank.getPort();
        if (SERVER_MAP.containsKey(key)) {
            IServerHandler serverHandler = SERVER_MAP.get(key);
            serverHandler.close();
            SERVER_MAP.remove(key);
        }
        db.update(Entity.create(getTableName()).set("status", Boolean.FALSE), where);
    }

    @Override
    @SneakyThrows
    public double getFailureRateByCode(String bankCode) {

        SqlBuilder sqlBuilder = getSqlBuilder().append(" and code = '"+bankCode+"'");

        Entity entity = db.queryOne(sqlBuilder.build());
        int failureRate = entity.get("failure_rate", 0);

        return failureRate == 0 ? 0 : Convert.toDouble(failureRate) / 100;
    }
}
